﻿/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */

using System;
using UnitedBinary.HarnessIt.SharedTypes;
using UnitedBinary.HarnessIt.TestEngine;
using UnitedBinary.UnitTests.Framework;
using System.Collections.Generic;

namespace UnitedBinary.HarnessIt.ExecutionCore
{
	public class TestExecutor
	{
		public event EventHandler<TestAssemblyEventArgs> AssemblyExecuted;
		public event EventHandler<TestAssemblyEventArgs> AssemblyExecuting;
		public event EventHandler<TestClassEventArgs> ClassExecuted;
		public event EventHandler<TestClassEventArgs> ClassExecuting;
		public event EventHandler<TestMethodArgs> MethodExecuted;
		public event EventHandler<TestMethodArgs> MethodExecuting;
		public event EventHandler<EventArgs> EngineStatusChanged;
		public event EventHandler<EventArgs> StatusTextChanged;

		private ITestEngine engine;
		private string engineStatus;
		private string statusText;
		private int methodsRun = 0;
		private int methodsTotal = 1;

		private IClass lastClass = null;
		private IAssembly lastAssembly = null;

		public string EngineStatus
		{
			get
			{
				return engineStatus;
			}
			private set
			{
				engineStatus = value;
				OnEngineStatusChanged();
			}
		}
		
		public string StatusText
		{
			get
			{
				return statusText;
			}
			private set
			{
				statusText = value;
				OnStatusTextChanged();
			}
		}

		public TestExecutor(ITestEngine engine)
		{
			this.engine = engine;
			EngineStatus = "Idle";
		}

		public Method[] GetAllActiveAndRequiredMethods(Assembly[] assemblies)
		{
			List<Method> methods = new List<Method>();
			foreach (Assembly A in assemblies)
			{
				if (!A.ContainsActiveTests && !A.ContainsRequiredTests)
				{
					continue;
				}

				foreach (Class c in A.Classes)
				{
					if (!c.ContainsActiveTests && !c.ContainsRequiredTests)
					{
						continue;
					}

					foreach (Method method in c.Methods)
					{
						if (method.Active || method.TestRequired)
						{
							methods.Add(method);
						}
					}
				}
			}
			return methods.ToArray();
		}

		public void Run(Method[] activeMethods)
		{
			try
			{
				InitializeExecutionEngine();
				RunInternal(activeMethods);
			}
			finally
			{
				ShutdownExecutionEngine();
			}
		}

		private void RunInternal(Method[] activeMethods)
		{
			List<Method> orderedMethods = GetOrderedMethods(activeMethods);

			// TODO: Make order independent (now depends on all tests in one class together)
			methodsRun = 0;
			methodsTotal = activeMethods.Length;

			ReportProgress();

			if (activeMethods.Length == 0)
			{
				return;
			}

			EngineStatus = "Running tests";
			ResetTests(activeMethods);

			foreach (Method method in orderedMethods)
			{
				RunMethodInternal(method);
			}
		}

		private static List<Method> GetOrderedMethods(Method[] activeMethods)
		{
			List<Method> orderedMethods = new List<Method>(activeMethods);
			//orderedMethods.Sort((m1, m2) =>
			//    {
			//        if (m1.ParentClass.ParentAssembly.FullPathName != m2.ParentClass.ParentAssembly.FullPathName)
			//        {
			//            return m1.ParentClass.ParentAssembly.Name.CompareTo(m2.ParentClass.ParentAssembly.Name);
			//        }

			//        if (m1.ParentClass.FullName != m2.ParentClass.FullName)
			//        {
			//            return m1.ParentClass.FullName.CompareTo(m2.ParentClass.FullName);
			//        }

			//        // must still use m1.OrderMode

			//        return 0;
			//    });
			return orderedMethods;
		}
		private void RunMethodInternal(Method method)
		{
			OnAssemblyExecuting(method.ParentClass.ParentAssembly);
			OnClassExecuting(method.ParentClass);
			OnMethodExecuting(method);

			engine.RunTest(method);
			
			OnMethodExecuted(method);
			OnClassExecuted(method.ParentClass);
			OnAssemblyExecuted(method.ParentClass.ParentAssembly);

			lastClass = method.ParentClass;
			lastAssembly = method.ParentClass.ParentAssembly;

			methodsRun++;
			ReportProgress();
		}

		private static void ResetTests(Method[] activeMethods)
		{
			// foeach A, A.reset tests.

			foreach (Method method in activeMethods)
			{
				method.ParentClass.ParentAssembly.ResetTests();
			}
		}

		private void ReportProgress()
		{
			// something with methodsRun & progress.
		}

		private void InitializeExecutionEngine()
		{
			EngineStatus = "Initializing unit test execution engine ... ";

			if (engine == null)
			{
				throw new Exception("The test execution engine has not be set.  There is no engine to execute tests.");
			}

			StatusText = engine.Initialize();
		}

		private void ShutdownExecutionEngine()
		{
			EngineStatus = "Shutting down unit test execution engine ... ";
			
			try
			{
				StatusText = engine.Shutdown();
			}
			catch (Exception e)
			{
				StatusText = "Error shutting down execution engine.\n\nDetails:\n" + e;
			}
		}

		protected virtual void OnMethodExecuted(IMethod method)
		{
			if (MethodExecuted != null)
			{
				MethodExecuted(this, new TestMethodArgs(method));
			}
		}

		protected virtual void OnMethodExecuting(IMethod method)
		{
			if (MethodExecuting != null)
			{
				MethodExecuting(this, new TestMethodArgs(method));
			}
		}
		protected virtual void OnEngineStatusChanged()
		{
			if (EngineStatusChanged != null)
			{
				EngineStatusChanged(this, EventArgs.Empty);
			}
		}

		protected virtual void OnStatusTextChanged()
		{
			if (StatusTextChanged != null)
			{
				StatusTextChanged(this, EventArgs.Empty);
			}
		}

		public virtual void OnAssemblyExecuted(IAssembly assembly)
		{
			if (AssemblyExecuted != null && lastAssembly != null && lastAssembly.FullPathName != assembly.FullPathName)
			{
				AssemblyExecuted(this, new TestAssemblyEventArgs(lastAssembly));
			}
		}
		public virtual void OnAssemblyExecuting(IAssembly assembly)
		{
			if (AssemblyExecuting != null && lastAssembly != null && lastAssembly.FullPathName != assembly.FullPathName)
			{
				AssemblyExecuting(this, new TestAssemblyEventArgs(assembly));
			}
		}
		public virtual void OnClassExecuted(IClass theClass)
		{
			if (ClassExecuted != null && lastClass != null && lastClass.FullName != theClass.FullName)
			{
				ClassExecuted(this, new TestClassEventArgs(lastClass));
			}
		}
		public virtual void OnClassExecuting(IClass theClass)
		{
			if (ClassExecuting != null && lastClass != null && lastClass.FullName != theClass.FullName)
			{
				ClassExecuting(this, new TestClassEventArgs(theClass));
			}
		}
	}
}